# coding=utf-8

from selenium import webdriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.select import Select
from selenium.common.exceptions import *
import traceback
import time
import unittest

from src.config import Config
from src.log import Log
from src.newselenium.by import By
from src.newselenium.element import Element

# 记录日志
logger = Log().getLogger


class Driver(object):
    """
    说明：
        重写selenium的webdriver
        self.driver 为selenium的原生webdriver类对象
    """

    def __init__(self, url, browser=""):
        """
        说明：
            实例化出driver对象
        :param url: 访问地址
        :param browser: 浏览器类型
        """
        self.driver = None # 浏览器 <class 'WebDriver'>
        try:
            if not str.strip(browser): # 如果没有指定浏览器类型，读取配置中设置的浏览器类型
                browser = str.strip(Config.browserType)
            if str.lower(browser) == "chrome":
                driver_remote = Config.projectDir + "\\drivers\\chromedriver.exe"
                self.driver = webdriver.Chrome(executable_path=driver_remote)
                # 静态超时时间
                self.driver.implicitly_wait(Config.timeOut) # 2018-6-10 改为根据浏览器类型区分，火狐浏览器对这一方法兼容性较差，可能会导致报错
            elif str.lower(browser) == "firefox":
                driver_remote = Config.projectDir + "\\drivers\\geckodriver.exe"
                self.driver = webdriver.Firefox(executable_path=driver_remote)
                logger.info("当前使用的是火狐浏览器，初始化时未指定静态超时时间，可以通过driver.implicitly_wait() 方法设置")
            else:
                raise TypeError("未找到指定的浏览器及其对应的驱动...")
            # 打开网页
            self.driver.get(url=url)
        except Exception as e:
            logger.error(traceback.format_exc())

    # --------------以下为重写的方法,方法名和源码一致，某些调用更方便,功能更强大-----------------

    def move_to_element(self, to_element):
        """
        说明：
            鼠标移动到指定元素上
        :param to_element: 需要移动到的元素 type = <class Element>
        """
        ActionChains(self.driver).move_to_element(to_element.changeToWebElement).perform()

    def drag_and_drop(self, source, target):
        """
        说明：
            将元素source拖动并释放到元素target处
        :param source: 需要移动的元素
        :param target: 目标元素
        示例：
            driver.drag_and_drop(source, target)
        注意：
            在拖拽元素时，即使匹配source元素的规则的在拖拽过程中发生变化，也可以完成拖拽
        """
        ActionChains(self.driver).drag_and_drop(source=source.changeToWebElement(), target=target.changeToWebElement()).perform()
        # ActionChains(self.driver).click_and_hold(source).move_to_element(target).release().perform()

    def drag_and_drop_by_offset(self, source, xoffset: int, yoffset: int):
        """
        说明：
            将元素source拖动到相对自身x, y坐标位置并释放
        :param source: 需要移动的元素
        :param xoffset: x方向像素值
        :param yoffset: y方向像素值
        示例：
            driver.drag_and_drop_by_offset(source, 500, 0)
        注意：
            在拖拽元素时，即使匹配source元素的规则的在拖拽过程中发生变化，也可以完成拖拽
        """
        ActionChains(self.driver).drag_and_drop_by_offset(source=source.changeToWebElement(), xoffset=xoffset, yoffset=yoffset).perform()


    def switch_to_frame(self, frame_reference):
        """
        说明：
            切换iframe/frame窗口
            可以根据iframe/frame标签的id、name属性，也可以通过getelement()返回的Element对象切换进去
            如果没有找到则会切换到最上层Top Window
        :param frame_reference: 
        """
        if isinstance(frame_reference, (int, str)):
            self.driver.switch_to.frame(frame_reference=frame_reference)
        elif isinstance(frame_reference, (Element)):
            self.driver.switch_to.frame(frame_reference=frame_reference.changeToWebElement())
        else:
            self.driver.switch_to.default_content() # 切换到最上层页面

    def switch_to_default_content(self):
        """
        说明：
            切换到最上层页面 Top Window
        """
        self.driver.switch_to.default_content()

    def switch_to_window(self, window_name):
        """
        说明：
            切换窗口句柄(标签页)
        :param window_name: 
            1.可以使用Driver.window_handles 返回的句柄进行切换
        示例:
            1. driver.switch_to_window(driver.window_handles[0])
        """
        self.driver.switch_to.window(window_name=window_name)

    def switch_to_alert(self):
        """
        说明：
            Switches focus to an alert on the page.
            将焦点切换到页面的提示框上(alert)，可以使用返回的alert对象操作提示框
        :return: 提示框alert对象 type = <class Alert >
        """
        return self.driver.switch_to.alert

    def execute_script(self, script, *args):
        """
        说明：
            执行 js 命令
        :param script: js 脚本 type = <class str>
        :param args: 可以是Element对象
        示例：
            driver.execute_script("arguments[0].scrollIntoView();", Element)
            driver.execute_script("return arguments[0].scrollHeight;", Element)
        """
        # 如果参数里面有Element的实例化对象，需要将其转化为selenium.webdriver.remote.webelement.WebElement对象
        args = (arg.element if isinstance(arg, Element) else arg for arg in args)
        return self.driver.execute_script(script, *args)

    def get(self, url):
        """
        说明：
            在当前窗口请求地址url
        """
        self.driver.get(url=url)

    def back(self):
        """
        说明：
            在浏览器历史中向后退一步
        """
        self.driver.back()

    def forward(self):
        """
        说明：
            在浏览器历史上前进一步
        """
        self.driver.forward()

    def close(self):
        """
        说明：
            关闭当前窗口
        """
        self.driver.close()

    def quit(self):
        """
        说明：
            关闭所有窗口并退出浏览器
        """
        self.driver.quit()

    def maximize_window(self):
        """
        说明：
            最大化浏览器窗口
        """
        self.driver.maximize_window()

    def minimize_window(self):
        """
        说明：
            最小化浏览器窗口
        """
        self.driver.minimize_window()

    def refresh(self):
        """
        说明：
            刷新当前页面
        """
        self.driver.refresh()

    def get_window_position(self):
        """
        说明：
            返回当前浏览器的 x,y 坐标
        :return: {'y': 10, 'x': 10} type = <class dict>
        """
        return self.driver.get_window_position()

    def get_window_rect(self):
        """
        说明：
            返回当前浏览器的位置
        :return:
        """
        return self.driver.get_window_rect()

    def get_window_size(self):
        """
        说明：
            返回当前浏览器的位置
        :return: {'width': 945, 'height': 1030} type = <class dict>
        """
        return self.driver.get_window_size()

    def set_window_position(self, x, y, windowHandle='current'):
        """
        说明：
            设置浏览器的位置
        :param x: 
        :param y: 
        :param windowHandle: 
        """
        self.driver.set_window_position(x=x, y=y, windowHandle=windowHandle)

    def set_window_rect(self, x=None, y=None, width=None, height=None):
        """
        说明：
            设置浏览器的位置与大小
        :param x: 
        :param y: 
        :param width: 
        :param height: 
        """
        self.driver.set_window_rect(x=x, y=y, width=width, height=height)

    def set_window_size(self, width, height, windowHandle='current'):
        """
        说明：
            设置浏览器的大小
        :param width: 
        :param height: 
        :param windowHandle: 
        """
        self.driver.set_window_size(width=width, height=height, windowHandle=windowHandle)

    def implicitly_wait(self, time_to_wait: int) -> None:
        self.driver.implicitly_wait(time_to_wait=time_to_wait)

    @property
    def window_handles(self):
        """
        说明：
            返回当前浏览器的窗口句柄
        :return: <type list>
        """
        return self.driver.window_handles

    @property
    def current_window_handle(self):
        """
        说明：
            返回当前窗口句柄
        :return: type = <class str>
        """
        return self.driver.current_window_handle

    @property
    def current_url(self):
        """
        说明：
            返回当前窗口请求的url地址
        :return: type = <class str>
        """
        return self.driver.current_url

    @property
    def title(self):
        """
        说明：
            返回当前窗口的标题
        :return: type = <class str>
        """
        return self.driver.title

    # --------------以下为新命名的方法-----------------

    def getelement(self, by=By.ID, value=None):
        """
        说明：
            定位并返回元素
        :param by: By.ID By.XPATH By.LINK_TEXT By.PARTIAL_LINK By.NAME By.TAG_NAME By.CLASS_NAME By.CSS_SELECTOR type = <class By>
        :param value: 匹配规则 type = <class str>
        :return: type = <class Element>
        示例：
            element = driver.getelement(By.ID, "id_name")
        """
        return Element(self.driver, self.driver.find_element(by=by, value=value))

    def getelements(self, by=By.ID, value=None):
        """
        说明：
            定位并返回元素
        :param by: By.ID By.XPATH By.LINK_TEXT By.PARTIAL_LINK By.NAME By.TAG_NAME By.CLASS_NAME By.CSS_SELECTOR type = <class By>
        :param value: 匹配规则 type = <class str>
        :return: type = <class list>
        示例：
            elements = driver.getelements(By.ID, "id_name")
        """
        elements = self.driver.find_elements(by=by, value=value)            # 如果没找到返回空列表
        return list(map(lambda ele: Element(self.driver, ele), elements))


    def doubleClick(self, element):
        """
        说明：
            鼠标双击指定的element
        :param element: element type = <class Element>
        """
        ActionChains(self.driver).double_click(element.changeToWebElement()).perform()

    def leftClick(self, element):
        """
        说明：
            鼠标左击指定的element
        :param element: element type = <class Element>
        """
        ActionChains(self.driver).click(element.changeToWebElement()).perform()

    def rightClick(self, element):
        """
        说明：
            鼠标右击指定的element
        :param element: element type = <class Element>
        """
        ActionChains(self.driver).context_click(element.changeToWebElement()).perform()

    def switch_to_window_byTagName(self, tag_name):
        """
        说明：
            切换窗口句柄(标签页)
        :param tag_name: 
            1.可以使用窗口的标签页名称进行切换
        示例:
            1. driver.switch_to_window_byTagName("百度一下，你就知道")
        """
        time_out = 10
        clock = 0
        while clock < 10:
            for handle in self.driver.window_handles:
                self.driver.switch_to.window(handle)
                if self.driver.title == tag_name:
                    print(self.driver.title)
                    return
            time.sleep(1)
            clock += 1
        raise TimeoutError("未找到您提供“{}”的标签名".format(tag_name) )

    def operateAlert(self, mode="ACCEPT"):
        """
        说明：
            操作当前窗口的alert提示框，也支持confirm、prompt对话框
        :param mode: 
            1. "ACCEPT" 点击提示框确认按钮
            2. "DISMISS" 点击提示框右上角的“x”
            3. "TEXT" 返回提示框的文字信息
        :return: 如果mode="TEXT"将返回提示框的内容
        """
        alert = self.driver.switch_to.alert
        if mode.upper() == "ACCEPT":
            alert.accept()
        elif mode.upper() == "DISMISS":
            alert.dismiss()
        elif mode.upper() == "TEXT":
            return alert.text

    def open_newindow(self):
        """
        说明：
            打开新窗口(标签页)
        """
        self.execute_script("window.open()")

    def get_screenshot(self): # TODO: 需要支持保存地址可配
        """
        说明：
            截图并保存到指定log目录 
        """
        time_now = time.strftime("%Y%m%d-%H%M%S", time.localtime())
        path = Config.projectDir + "\\log\\" + time_now + ".png"
        self.driver.get_screenshot_as_file(filename=path)

    def scrollToBottom(self):
        """
        说明：
            滚动滚动条到底部
        """
        self.driver.execute_script("window.scrollTo(0, 999999)")

    def scrollToUp(self):
        """
        说明：
            滚动滚动条到顶部
        """
        self.driver.execute_script("window.scrollTo(0, 0)")

    def scrollToXY(self, x=0, y=0):
        """
        说明：
            滚动到相对于左上角的(x, y)位置
        """
        self.driver.execute_script("window.scrollTo({}, {})".format(x, y))

    def focusElement(self, element):
        """
        说明：
            视图定位到指定的element上
        :param element: Element对象 <class 'Element'>
        """
        self.execute_script("arguments[0].scrollIntoView();", element)

    def changeToWebDriver(self):
        """
        说明：
            返回原生webdriver对象
        :return: 
        """
        return self.driver
